feat: add Jenkinsfile pipeline extractor - #2754
Conversation
f1b3f78 to
afa24fc
Compare
afa24fc to
93e9a61
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.
Formal verification. 1 change(s) tested, no difference found (not proven).
Graphify review — findings
This PR appears to primarily involve version/changelog reshuffling and a feature addition, alongside broad test changes. - Changelog/README: Moves the 0.9.44 unreleased notes back into a 0.9.43 (unreleased) section (unreleasing what was marked 0.9.43), removes YouTube badge links from the README, and adds a README table entry documenting Jenkins Pipeline (Jenkinsfile) support. - New extractor: Adds a Jenkins/Groovy DSL extractor (graphify_extractors_jenkins_extract_jenkinsfile_walk) and related surface, touching the extractors engine and CLI/hook code paths. - affected.py: Reverts the root parameter threading on _as_repo_relative, resolve_seed, and format_affected, going back to anchoring absolute-path seeds against Path.cwd() instead of a graph-derived root. The bulk of the changed symbols are test files across install, detect, extract, languages, and hooks modules, suggesting wide test churn accompanying these changes.
Worth a look
- resolve_seed public signature narrowed by removing root parameter —
graphify/affected.py:130· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
- format_affected public signature narrowed by removing root parameter —
graphify/affected.py:256· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3493 functions depend on the 2392 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 454 callers, 41 callees - new:
_rebuild_code()— 95 callers, 51 callees - new:
detect()— 93 callers, 14 callees - new:
_extract_generic()— 18 callers, 23 callees - new:
save_manifest()— 34 callers, 11 callees - new:
extract_files_direct()— 17 callers, 20 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_corpus_parallel()— 26 callers, 10 callees - …and 73 more
Verification — 3493 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 3493 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify \_as\_repo\_relative.
The verifier did not have enough to check \_as\_repo\_relative, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path | None` — outside the synthesizable primitive/collection set
No difference found (not proven): No behavior difference found in format\_affected (not a proof).
The verifier ran both versions of format\_affected on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify resolve\_seed.
The verifier did not have enough to check resolve\_seed, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify dispatch\_command.
The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify classify\_file.
The verifier did not have enough to check classify\_file, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify detect.
The verifier did not have enough to check detect, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify ignored\_predicate.
The verifier did not have enough to check ignored\_predicate, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify collect\_files.
The verifier did not have enough to check collect\_files, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `target` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify extract\_cpp.
The verifier did not have enough to check extract\_cpp, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_require\_imports\_js.
The verifier did not have enough to check \_require\_imports\_js, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify extract\_ocaml.
The verifier did not have enough to check extract\_ocaml, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify install.
The verifier did not have enough to check install, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_install\_hook.
The verifier did not have enough to check \_install\_hook, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `hooks_dir` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify status.
The verifier did not have enough to check status, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify install.
The verifier did not have enough to check install, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
· 3 grounded finding(s) anchored inline below; 78 more finding(s) on lines outside this diff (see the check run).
| @@ -4989,6 +4990,8 @@ def _is_cpp_header(path: Path) -> bool: | |||
|
|
|||
| def _get_extractor(path: Path) -> Any | None: | |||
There was a problem hiding this comment.
_get_extractor()
fans out to 6 callees (efferent coupling); 29 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return f"L{node.start_point[0] + 1}" | ||
|
|
||
|
|
||
| def extract_jenkinsfile(path: Path) -> dict: |
There was a problem hiding this comment.
extract_jenkinsfile()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| add_edge(file_nid, pipeline_nid, "contains", node) | ||
| return pipeline_nid | ||
|
|
||
| def walk(node: Any, parent: str, *, in_steps: bool = False, current_stage: str | None = None, |
There was a problem hiding this comment.
walk()
fans out to 11 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Summary
callsedges between internal Groovy functionssh '...',checkout scm,docker.build(...), and@Library(...).groovyextraction behaviorGraph structure
The extractor emits:
JenkinsPipelineJenkinsParallelandJenkinsMatrixnodesgroovy_functionnodesIt emits
contains,calls,uses_image,builds, anduses_libraryrelationships.Follow-up work
Cross-format resolvers for Dockerfiles, Kubernetes/Helm, and Terraform will be added separately. External multibranch Jenkins configuration and shared-library source resolution are intentionally outside this PR.